/******************************************************************************
 * Copyright (C) 671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "NodeCpp/Event_POSIX.h"
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
#include <ctime>
#include <new>
#include "NodeCpp/Macros.h"
#include "NodeCpp/AutoPtr.h"
using NodeCpp::AutoPtr;

///////////////////////////////////////////////////////////////////////////////
struct internal_event_t {
	bool State;
	bool ManualReset;
	pthread_mutex_t Mutex;
	pthread_cond_t Cond;
};

struct internal_event_t* internal_event_create(bool _ManualReset, bool _InitState)
{
	AutoPtr<struct internal_event_t> _Event(new (std::nothrow) struct internal_event_t);
	if (!_Event) {
		return NULL;
	}
	_Event->State = _InitState;
	_Event->ManualReset = _ManualReset;

	if (pthread_mutex_init(&(_Event->Mutex), NULL)) {
		return NULL;
	}

	if (pthread_cond_init(&(_Event->Cond), NULL)) {
		pthread_mutex_destroy(&(_Event->Mutex));
		return NULL;
	}

	return _Event.release();
}

void internal_event_destroy(struct internal_event_t* _Event)
{
	AutoPtr<struct internal_event_t> _DelEvent(_Event);
	pthread_cond_destroy(&(_DelEvent->Cond));
	pthread_mutex_destroy(&(_DelEvent->Mutex));
}

int internal_event_set(struct internal_event_t* _Event)
{
	if (pthread_mutex_lock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	{
		_Event->State = true;

		if (_Event->ManualReset) {
			if (pthread_cond_broadcast(&(_Event->Cond))) {
				return -1;
			}
		} else {
			if (pthread_cond_signal(&(_Event->Cond))) {
				return -1;
			}
		}
	}

	if (pthread_mutex_unlock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	return 0;
}

int internal_event_reset(struct internal_event_t* _Event)
{
	if (pthread_mutex_lock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	{
		_Event->State = true;
	}

	if (pthread_mutex_unlock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	return 0;
}

int internal_event_wait(struct internal_event_t* _Event)
{
	if (pthread_mutex_lock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	{
		while (!_Event->State) {
			if (pthread_cond_wait(&(_Event->Cond), &(_Event->Mutex))) {
				pthread_mutex_unlock(&(_Event->Mutex));
				return -1;
			}
		}

		if (!_Event->ManualReset) {
			_Event->State = false;
		}
	}

	if (pthread_mutex_unlock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	return 0;
}

int internal_event_timedwait(struct internal_event_t* _Event, long _Milliseconds)
{
	int _Rc = 0;
	struct timespec _Abstime;
	struct timeval _Tv;
	gettimeofday(&_Tv, NULL);
	_Abstime.tv_sec = _Tv.tv_sec + _Milliseconds / 1000;
	_Abstime.tv_nsec = _Tv.tv_usec * 1000 + (_Milliseconds % 1000) * 1000000;
	if (_Abstime.tv_nsec >= 1000000000) {
		_Abstime.tv_nsec -= 1000000000;
		_Abstime.tv_sec += 1;
	}

	if (pthread_mutex_lock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	{
		while (!_Event->State) {
			if ((_Rc = pthread_cond_timedwait(&(_Event->Cond), &(_Event->Mutex), &_Abstime))) {
				if (_Rc == ETIMEDOUT) break;
				pthread_mutex_unlock(&(_Event->Mutex));
				return -1;
			}
		}

		if (_Rc == 0 && !_Event->ManualReset) {
			_Event->State = false;
		}
	}

	if (pthread_mutex_unlock(&(_Event->Mutex)) != 0) {
		return -1;
	}

	return 0;
}
///////////////////////////////////////////////////////////////////////////////

namespace NodeCpp
{
    EventImpl::EventImpl(bool _AutoReset, bool _InitState)
        : Event_(NULL)
    {
    	Event_ = internal_event_create(!_AutoReset, _InitState);
    	NODECPP_COND_THROW(Event_ == NULL,
    			std::runtime_error("Failed create event."));
    }

    EventImpl::~EventImpl(void)
    {
    	if (Event_ != NULL) {
    		internal_event_destroy(Event_);
    	}
    }

    void EventImpl::SetImpl(void)
    {
    	NODECPP_COND_THROW(internal_event_set(Event_) != 0,
    			std::runtime_error("Failed set event."));
    }

    void EventImpl::ResetImpl(void)
    {
    	NODECPP_COND_THROW(internal_event_reset(Event_) != 0,
    	    	std::runtime_error("Failed reset event."));
    }

    void EventImpl::WaitImpl(void)
    {
    	NODECPP_COND_THROW(internal_event_wait(Event_) != 0,
    			std::runtime_error("Failed wait event."));
    }

    void EventImpl::WaitImpl(long _Milliseconds)
    {
        NODECPP_COND_THROW(internal_event_timedwait(Event_, _Milliseconds) != 0,
            	std::runtime_error("Failed wait event."));
    }
}
